package com.owc.service.impl.admin;

import com.alibaba.fastjson.JSON;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.owc.common.Const.ConfigConst;
import com.owc.common.Const.Const;
import com.owc.common.enums.CallbackTypeEnum;
import com.owc.common.enums.ExceptionEnum;
import com.owc.common.enums.FlowTypeEnum;
import com.owc.common.enums.StatusEnum;
import com.owc.common.exception.BadRequestException;
import com.owc.common.redis.CacheBuildKey;
import com.owc.common.redis.RedisUtil;
import com.owc.common.sdk.request.CancelOrderRequest;
import com.owc.common.sdk.response.CancelOrderResponse;
import com.owc.common.tool.BeanUtils;
import com.owc.common.tool.UtilsService;
import com.owc.common.tool.ValidationUtils;
import com.owc.dao.DrawFlowMapper;
import com.owc.dao.TokenMapper;
import com.owc.dao.TransferRecordMapper;
import com.owc.dao.WithdrawFlowMapper;
import com.owc.domain.BaseResult;
import com.owc.domain.dto.admin.WithdrawDetailDTO;
import com.owc.domain.dto.admin.WithdrawListDTO;
import com.owc.domain.entity.Token;
import com.owc.domain.entity.TransferRecord;
import com.owc.domain.entity.User;
import com.owc.domain.entity.WithdrawFlow;
import com.owc.domain.vo.admin.BatchReviewVo;
import com.owc.domain.vo.admin.CallbackRequest;
import com.owc.domain.vo.admin.WithdrawVo;
import com.owc.service.WalletServer;
import com.owc.service.admin.FinanceService;
import com.owc.service.admin.SystemService;
import com.owc.service.app.UserService;
import com.owc.service.impl.app.TransactionService;
import jdk.net.SocketFlow;
import lombok.extern.slf4j.Slf4j;
import net.bytebuddy.asm.Advice;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;

/**
 * @Author xiaom
 * @Date 2019/12/12 17:03
 * @Version 1.0.0
 * @Description <>
 **/
@Service
@Slf4j
public class FinanceServiceImpl implements FinanceService {

    @Resource
    private WithdrawFlowMapper withdrawFlowMapper;
    @Resource
    private TransferRecordMapper transferRecordMapper;
    @Resource
    private TokenMapper tokenMapper;
    @Resource
    private DrawFlowMapper drawFlowMapper;

    @Autowired
    private TransactionService transactionService;
    @Autowired
    private WalletServer walletServer;
    @Autowired
    private UserService userService;
    @Autowired
    private SystemService systemService;

    @Override
    public BaseResult<List<WithdrawListDTO>> getWithdrawList(WithdrawVo withdrawVo) {
        PageHelper.startPage(withdrawVo.getPageIndex(),withdrawVo.getPageSize());
        List<WithdrawFlow> withdrawListDTOS = withdrawFlowMapper.selectByQueryModel(withdrawVo);
        PageInfo<WithdrawFlow> pageInfo = new PageInfo<>(withdrawListDTOS);
        return BaseResult.success(BeanUtils.coventObject(withdrawListDTOS,WithdrawListDTO.class),pageInfo.getTotal());
    }

    @Override
    public Boolean batchReview(BatchReviewVo batchReviewVo) throws Exception {

        Optional<StatusEnum> enumOptional = StatusEnum.match(batchReviewVo.getStatus());
        if(!enumOptional.isPresent()){
            throw new BadRequestException(ExceptionEnum.ENUM_FOUND_ERR);
        }

        //获取所有订单详细数据
        List<WithdrawFlow> withdrawFlows = withdrawFlowMapper.selectByIds(batchReviewVo.getTransferIds());
        //检查是否存在多个币种,是否存在不满足钱包地址规则的订单
        beforeValid(withdrawFlows);

        switch (enumOptional.get()){
            case REFUSE:
                //拒绝这批订单,更改数据库状态.返回冻结的金额
                refuseOrder(withdrawFlows);
                break;
            case PASSED:
                assertEqualsStatus(withdrawFlows,StatusEnum.PENDING);
                //通过这批订单,开始处理
                passedOrder(withdrawFlows,null);
                break;
        }
        return true;
    }

    @Override
    public Boolean retryReview(String transferId) {
        //获取提现详情信息
        WithdrawFlow withdrawFlow = withdrawFlowMapper.selectById(transferId);
        Assert.notNull(withdrawFlow,"不存在的提现订单号:[" + transferId +"]");
        assertEqualsStatus(Collections.singletonList(withdrawFlow),StatusEnum.FAIL,StatusEnum.CANCEL);
        passedOrder(Collections.singletonList(withdrawFlow),StatusEnum.FAIL);
        return true;
    }

    @Override
    public WithdrawDetailDTO getTransferDetail(String transferId) {

        WithdrawDetailDTO detailDTO = new WithdrawDetailDTO();

        //获取订单基本信息
        WithdrawFlow withdrawFlow = withdrawFlowMapper.selectById(transferId);
        Assert.notNull(withdrawFlow,"错误的订单号:["+transferId+"]");

        //获得订单基本信息
        detailDTO.setBasisInfo(transOrderBasisInfo(withdrawFlow));

        //获取区块链交易详情
        detailDTO.setDetail(getFlowsInfo(transferId));

        //获取流水详情
        detailDTO.setFlows(new ArrayList<>());

        return detailDTO;
    }

    @Override
    public List<WithdrawDetailDTO.DetailBean> getFlowsInfo(String transferId) {

        List<WithdrawDetailDTO.DetailBean> detailBeans = new ArrayList<>();

        List<TransferRecord> recordList = transferRecordMapper.selectByFlowId(transferId);
        if(recordList == null || recordList.isEmpty()){
            return detailBeans;
        }

        recordList.forEach(item -> {
            WithdrawDetailDTO.DetailBean detailBean = new WithdrawDetailDTO.DetailBean();
            detailBean.setCommitTime(item.getCommittime());
            detailBean.setGas(item.getGas() + "");
            detailBean.setStatus(item.getStatus());
            detailBean.setTransferAmount(item.getTransferamount());
            detailBean.setTxHash(item.getTxhash());
            detailBean.setBlockNumber(item.getBlocknumber());
            detailBean.setTransferId(transferId);
            detailBeans.add(detailBean);
        });
        return detailBeans;
    }

    private WithdrawDetailDTO.BasisInfoBean transOrderBasisInfo(WithdrawFlow withdrawFlow) {
        WithdrawDetailDTO.BasisInfoBean basisInfoBean = BeanUtils.transformFrom(withdrawFlow,WithdrawDetailDTO.BasisInfoBean.class);
        Assert.notNull(basisInfoBean,ExceptionEnum.UNKNOWN_ERR.getMessage());
        basisInfoBean.setTransferAmount(withdrawFlow.getTotalAmount());
        return basisInfoBean;
    }

    @Override
    public BaseResult<Boolean> callbackWallet(CallbackRequest callbackRequest) throws Exception {
        Optional<CallbackTypeEnum> typeEnumOptional = CallbackTypeEnum.match(callbackRequest.getTxnType());
        if(!typeEnumOptional.isPresent()){
            throw new BadRequestException(ExceptionEnum.TRANSFER_TYPE_ERR);
        }

        //判断是否属于取消类型的回调
        if(isCancelOrder(callbackRequest.getTxnHash())){
            log.info("回调订单属于[取消]类型,执行对应逻辑");
            //以取消的逻辑执行
            cancelHandler(callbackRequest);
            return BaseResult.success(null);
        }

        switch (typeEnumOptional.get()){
            case RECHARGE_TYPE:  //充值回调
                rechargeCallback(callbackRequest);
                break;
            case WITHDRAW_TYPE:  //提现回调
                withdrawCallback(callbackRequest);
                break;
            case CONFIRM_TYPE:   //确认区块数
                confirmCallback(callbackRequest);
                break;
            case DRAW_TYPE:  //划转
                drawCallback(callbackRequest);
                break;
            case TX_HASH_TYPE:  //基本交易hash回调
                txHashCallBack(callbackRequest);
                break;
            default:
                log.error("异常的回调类型:[{}]",typeEnumOptional.get().getType());
        }
        return BaseResult.success(null);
    }

    private void cancelHandler(CallbackRequest callbackRequest) {
        transactionService.updateCancelOrderStatus(callbackRequest);
    }

    @Override
    public BaseResult<Boolean> cancelOrder(String txHash) {

        TransferRecord transferRecord = transferRecordMapper.selectByTxHash(txHash);
        Assert.notNull(transferRecord,"交易记录中不存在此hash:" + txHash);
        Assert.isTrue(transferRecord.getStatus().equals(StatusEnum.PASSED.getType()),"只能取消状态为[通过]的订单");
        log.info("取消的订单详情:{}",JSON.toJSONString(transferRecord));
        CancelOrderResponse.ResponseMsgResult responseMsgResult;
        try {
            responseMsgResult = walletServer.cancelOrder(txHash);
            if(responseMsgResult.getStatus()){
                log.info("取消订单事件提交成功,新的交易hash:[{}] nonce:[{}]",responseMsgResult.getTxHash(),responseMsgResult.getNonce());
                //缓存尝试取消的交易hash,当钱包服务回调时,不做处理
                RedisUtil._REDIS_MODULE.sadd(CacheBuildKey.buildCancelOrderHashKey(),responseMsgResult.getTxHash());
                //缓存被替换的hash的映射
                RedisUtil._REDIS_MODULE.set(CacheBuildKey.buildReplaceTxHashKey(responseMsgResult.getTxHash()),transferRecord.getTxhash());
                //识别交易类型,并修改状态
                updateOrderStatus(transferRecord,StatusEnum.CANCELING);
            }
        } catch (Exception e) {
            log.error("取消交易失败,exMsg:{}",e.getMessage());
            e.printStackTrace();
            throw new BadRequestException(e.getMessage());
        }
        return BaseResult.success(true);
    }

    public void updateOrderStatus(@NonNull TransferRecord transferRecord,StatusEnum statusEnum) {

        String txType = transferRecord.getTxnttype();
        Optional<CallbackTypeEnum> typeEnumOptional = CallbackTypeEnum.match(txType);
        if(!typeEnumOptional.isPresent()){
            return;
        }
        switch (typeEnumOptional.get()){
            case WITHDRAW_TYPE:
                log.info("更新提现订单交易状态为:[{}]",statusEnum.getDesc());
                updateWithdrawStatus(transferRecord.getWithdrawflowid(),statusEnum);
                break;
            case DRAW_OUT_TYPE:
            case DRAW_IN_TYPE:
            case DRAW_TYPE:
                log.info("更新划转订单交易状态为:[{}]",statusEnum.getDesc());
                updateDrawStatus(transferRecord.getWithdrawflowid(),statusEnum);
                break;
            default:
                log.error("异常的txType:{}",txType);

        }
    }

    /**
     * 更新划转订单交易状态
     * @param withdrawflowid  订单ID
     * @param canceling   状态
     */
    private void updateDrawStatus(String withdrawflowid, StatusEnum canceling) {
        drawFlowMapper.updateStatusByTransferId(withdrawflowid,canceling.getType());
        transferRecordMapper.updateStatusByFlowId(withdrawflowid,canceling.getType());
    }
    /**
     * 更新提现订单交易状态
     * @param withdrawflowid  订单ID
     * @param canceling   状态
     */
    private void updateWithdrawStatus(String withdrawflowid, StatusEnum canceling) {
        withdrawFlowMapper.updateStatusByTransferId(withdrawflowid,canceling.getType());
        transferRecordMapper.updateStatusByFlowId(withdrawflowid,canceling.getType());
    }

    /**
     * 判断是否属于取消类型的订单
     * @param txHash  交易hash
     */
    private Boolean isCancelOrder(String txHash){
        return RedisUtil._REDIS_MODULE.isMember(CacheBuildKey.buildCancelOrderHashKey(),txHash);
    }

    private void txHashCallBack(CallbackRequest callbackRequest) {
        log.info("收到钱包Hash回调请求, 回调信息:[{}]",JSON.toJSONString(callbackRequest));
        transactionService.updateTxHashInfo(callbackRequest);
    }

    private void drawCallback(CallbackRequest callbackRequest) throws Exception {

        log.info("收到划转操作的交易回调请求,回调信息:[{}]",JSON.toJSONString(callbackRequest));
        validTxnHashIsRepeat(callbackRequest.getTxnHash());
        transactionService.updateDrawOrderInfo(callbackRequest);
        log.info("划转回调处理完毕. 交易单号:[{}]",callbackRequest.getTrace());
    }

    private void confirmCallback(CallbackRequest callbackRequest) throws Exception {

        log.info("收到钱包确认区块数回调请求,回调信息:[{}]",JSON.toJSONString(callbackRequest));
        Assert.notNull(callbackRequest.getTxnHash(),"异常的确认区块回调,因为txHash为空");
        transactionService.updateTransferConfirmBlockNumber(callbackRequest.getTxnHash(),callbackRequest.getConfirmBlockNumber());
    }

    private void rechargeCallback(CallbackRequest callbackRequest) {

        validTxnHashIsRepeat(callbackRequest.getTxnHash());

        log.info("收到钱包充币回调请求, 回调信息:[{}]",JSON.toJSONString(callbackRequest));
        Assert.notNull(callbackRequest.getAmount(),"异常的充币回调,因为amount字段为空");
        Assert.notNull(callbackRequest.getTokenName(),"异常的充币回调,因为tokenName字段为空");

        //根据address查找用户信息
        Optional<User> optionalUser = userService.selectByEthAddress(callbackRequest.getRecepient());
        if(!optionalUser.isPresent()){
            throw new BadRequestException("回调请求存在异常,因为回调的钱包地址不存在于数据库中");
        }

        User user = optionalUser.get();

        transactionService.saveRechargeInfo(user,callbackRequest);
    }

    private void withdrawCallback(CallbackRequest callbackRequest) throws Exception {

        validTxnHashIsRepeat(callbackRequest.getTxnHash());

        log.info("收到钱包提币回调请求, 回调信息:[{}]",JSON.toJSONString(callbackRequest));
        //修改withdraw_flow表订单状态,根据trace(transferId)为查询条件
        transactionService.updateWithdrawCallbackInfo(callbackRequest);
        log.info("提笔回调处理完毕. 交易单号:[{}]",callbackRequest.getTrace());
    }

    /**
     * 检查交易是否重复回调
     */
    private void validTxnHashIsRepeat(String txnHash){
        if(txnHash == null) return;
        //防止重复提交,需要同步锁控制
        if(!RedisUtil._REDIS_MODULE.tryGetLock(CacheBuildKey.buildLockKey(txnHash),"1")){
            log.info("Duplicate submissions by the wallet server. txnHash:[{}]",txnHash);
            throw new BadRequestException("重复的区块交易提交");
        }
    }

    private void beforeValid(List<WithdrawFlow> withdrawFlows) {
        String defaultMessage = "钱包地址可能不正确，请核对后再进行审核";
        Long tokenId = null;
        for (WithdrawFlow withdrawFlow : withdrawFlows) {
            if(tokenId == null){
                tokenId = withdrawFlow.getTokenId();
            }
            if(!tokenId.equals(withdrawFlow.getTokenId())){
                throw new BadRequestException("暂时只支持单一币种的批量转账审核");
            }
            if(UtilsService.isEth(getParentTokenName(withdrawFlow.getTokenName()))){
                Assert.isTrue(ValidationUtils.isEthWalletAddress(withdrawFlow.getWalletAddress()),defaultMessage);
            }else{
                Assert.isTrue(ValidationUtils.isBtcWalletAddress(withdrawFlow.getWalletAddress()),defaultMessage);
            }
        }
    }

    private void passedOrder(List<WithdrawFlow> withdrawFlows,StatusEnum statusEnum)  {
        platformBalanceCheck(withdrawFlows);
        log.info("批量审核订单前置条件通过,执行钱包调用操作.....");
        //保存流水信息
        List<TransferRecord> recordList = transactionService.createWithdrawFlow(withdrawFlows,StatusEnum.PASSED);

        //建立流水表与订单记录的映射
        HashMap<String,String> mapper = new HashMap<>();
        recordList.forEach(item -> {
            mapper.put(item.getWithdrawflowid(),item.getTransferid());
        });

        //替换追踪ID
        withdrawFlows.forEach(item -> {
            item.setTransferId(mapper.get(item.getTransferId()));
        });

        //调用钱包服务,进行提现操作
        if(!processWalletServer(withdrawFlows)){
            throw new BadRequestException(ExceptionEnum.BAD_REQUEST_ERR);
        }
    }

    private void platformBalanceCheck(List<WithdrawFlow> withdrawFlows) {
        //检查平台钱包余额是否足够当前批次订单
        BigDecimal platformBalance = platformWalletBalance(withdrawFlows.get(0).getTokenId());
        //获取当前批次订单总金额数量
        BigDecimal totalAmount = batchTotalAmount(withdrawFlows);
        log.info("当前批次订单总金额:[{}]",totalAmount);
        if(platformBalance.compareTo(totalAmount) < 0) {
            log.error("平台批量审核提现订单执行失败,平台可用余额:[{}],当前批次总金额:[{}]",platformBalance,totalAmount);
            throw new BadRequestException(ExceptionEnum.PLATFORM_BALANCE_LACK.getCNMessage());
        }

        //检查平台ETH是否低于配置值,以防手续费不够导致提现失败
        try {
            BigDecimal minEth = systemService.getConfigValue(ConfigConst.min_eth,BigDecimal.class);
            if(minEth == null){
                throw new BadRequestException("缺少min_eth配置参数");
            }
            BigDecimal ethBalance = walletServer.getPlatformBalance(Const.ETH,Const.ETH);
            if(ethBalance.compareTo(minEth) < 0){
                log.error("平台ETH余额不足,无法处理这笔提现请求. 平台ETH余额:[{}],系统配置最低值:[{}]",ethBalance,minEth);
                throw new BadRequestException(ExceptionEnum.PLATFORM_BALANCE_MISS.getCNMessage());
            }
        } catch (Exception e) {
            log.error("【提币】获取平台ETH余额失败, exMsg:{}",e.getMessage());
            throw new BadRequestException(e.getMessage());
        }
    }

    private Boolean processWalletServer(List<WithdrawFlow> withdrawFlows) {
        try {
            Token token = tokenMapper.selectByPrimaryKey(withdrawFlows.get(0).getTokenId());
            String chain;
            String tokenName;
            if(token.getParentId() == -1L){
                tokenName = null;
                chain = token.getTokenName();
            }else{
                Token parentToken = tokenMapper.selectByPrimaryKey(token.getParentId());
                tokenName = token.getTokenName();
                chain = parentToken.getTokenName();
            }
            return walletServer.batchWithdraw(withdrawFlows,chain,tokenName,null,CallbackTypeEnum.WITHDRAW_TYPE);
        } catch (Exception e) {
            log.error("批量提现订单失败,异常信息:[{}]",e.getMessage());
            throw new BadRequestException(ExceptionEnum.BAD_REQUEST_ERR);
        }
    }

    private BigDecimal batchTotalAmount(List<WithdrawFlow> withdrawFlows) {
        BigDecimal totalAmount = BigDecimal.ZERO;
        for (WithdrawFlow withdrawFlow : withdrawFlows) {
            totalAmount = totalAmount.add(withdrawFlow.getTotalAmount());
        }
        return totalAmount;
    }

    private BigDecimal platformWalletBalance(Long tokenId) {
        //获取当前批次的币种信息
        Token token = tokenMapper.selectByPrimaryKey(tokenId);
        if(token == null){
            throw new BadRequestException(ExceptionEnum.INVALID_TOKEN_ERR);
        }

       String parentTokenName = getParentTokenName(token);
        //调用钱包服务接口,查询平台当前币种的钱包余额,如果小于当前订单的总余额,则结束执行
        try {
            BigDecimal balance = walletServer.getPlatformBalance(parentTokenName,token.getTokenName());
            log.info("链:[{}],币种:[{}],当前可用余额:[{}]",parentTokenName,token.getTokenName(),balance);
            return balance;
        } catch (Exception e) {
            log.error("Getting platform wallet balance exception");
            throw new BadRequestException(ExceptionEnum.UNKNOWN_ERR);
        }
    }

    private String getParentTokenName(String tokenName){
        Token token = tokenMapper.selectByTokenName(tokenName);
        if(token == null){
            throw new BadRequestException(ExceptionEnum.INVALID_TOKEN_ERR);
        }
        return getParentTokenName(token);
    }

    private String getParentTokenName(Token token){
        Token parentToken;
        if(token.getParentId() != -1L){
            //需要获取父级类型,然后定义为chain
            parentToken = tokenMapper.selectByPrimaryKey(token.getParentId());
            return parentToken.getTokenName();
        }else{
            return token.getTokenName();
        }
    }

    private void refuseOrder(List<WithdrawFlow> withdrawFlows) throws Exception {
        //如果订单状态不是待审核或者取消成功,则跳过
        assertEqualsStatus(withdrawFlows,StatusEnum.PENDING);
        transactionService.refuse(withdrawFlows);
    }

    private void assertEqualsStatus(List<WithdrawFlow> withdrawFlows,StatusEnum...statusEnums){
        List<Integer> statusVals = new ArrayList<>();

        for (StatusEnum statusEnum : statusEnums) {
            statusVals.add(statusEnum.getType());
        }
        log.info("statusVals:[{}]",JSON.toJSONString(statusVals));
        for (WithdrawFlow withdrawFlow : withdrawFlows) {
            if(!statusVals.contains(withdrawFlow.getStatus())) {
                log.info("当前不满足的订单状态:{}",withdrawFlow.getStatus());
                throw new BadRequestException("订单:[" + withdrawFlow.getTransferId() +"] 不满足执行条件, 订单当前状态:[" + StatusEnum.match(withdrawFlow.getStatus()).get().getDesc() + "]");
            }
        }
    }
}
